package kibahed.uni.mdp.representation;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;

public class MDPField {

	private int rows;
	private int collums;
	private State[][] field;
	private List<State> stateList;
	private List<Position> noisyPositions;
	private List<Position> badPositions;
	private boolean walkOutAllowd = false;
	private Action stay = new Action(0,"stay");
	private Action up = new Action (1,"up");
	private Action right = new Action(2,"right");
	private Action down = new Action(3,"down");
	private Action left = new Action(4,"left");
	private double badProbability = 0.90;
	private double normalProbabaility = 0.9;
	private double noisyProbability = 0.25;
	private Position goalStatePosition;
	
	
	public MDPField(int rows, int collums,List<Position> noisyStates, List<Position> badStates) {
		this.rows = rows;
		this.collums = collums;
		field = new State[rows][collums];
		this.noisyPositions = noisyStates;
		this.badPositions = badStates;
		stateList = new ArrayList<State>();
		createField();
	}
	
	public void createField() {
		for(int i = 0; i < rows; i++) {
			for(int j = 0; j < collums; j++) {
				Position position = new Position(i, j);
				State state = new State(position);
				field[i][j] = state;
				stateList.add(state);
			}
		}
		connectStates();
	}
	
	private void connectStates() {
		for(int i = 0; i < rows; i++) {
			for(int j = 0; j < collums; j++) {
				List<Connection> connections = getConnections(i, j);
				field[i][j].setConnections(connections);
				setProbabilites(i, j);
			}
		}
	
	}
	
	private List<Connection> getConnections(int row, int collum) {
		List<Connection> connections = new ArrayList<Connection>();
		Map<Action,Double> rewards = new HashMap<Action,Double>();
		Connection cStay = new Connection(field[row][collum], stay, 0);
		State state = field[row][collum];
		rewards.put(stay, state.getDefaultReward());
		connections.add(cStay);
		if(row!=0) {
			Connection cUp = new Connection(field[row-1][collum],up,0);
			connections.add(cUp);
			rewards.put(up, state.getDefaultReward());
		} else if(walkOutAllowd){
			rewards.put(up, state.getWalkOutsideReward());
		}
		if(row!= rows-1) {
			Connection cDown = new Connection(field[row+1][collum],down,0);
			connections.add(cDown);
			rewards.put(down, state.getDefaultReward());
		} else if(walkOutAllowd) {
			rewards.put(down, state.getWalkOutsideReward());
		}
		if(collum!=0) {
			Connection cLeft = new Connection(field[row][collum-1],left,0);
			connections.add(cLeft);
			rewards.put(left, state.getDefaultReward());
		} else if(walkOutAllowd) {
			rewards.put(left,state.getWalkOutsideReward());
		}
		if(collum!=collums-1) {
			Connection cRight = new Connection(field[row][collum+1],right,0);
			connections.add(cRight);
			rewards.put(right,state.getDefaultReward());
		} else if(walkOutAllowd) {
			rewards.put(right, state.getWalkOutsideReward());
		}
		state.setRewards(rewards);
		return connections;
	}
	
	private void setProbabilites(int row, int collum) {
		
		State start = field[row][collum];
		
		Map<Action,Map<Action,Double>> probs = new HashMap<Action,Map<Action,Double>>();
		
		if(start.isAbsorbing()) {
			for(Action a : start.getActions()) {
				Map<Action,Double> probA = new HashMap<Action,Double>();
				double sum=0;
				for(Action b : start.getActions()) {
					if(b.equals(stay)) {
						probA.put(b, 1.);
						sum+=1;
					} else {
						probA.put(b, 0.);
						sum+=0;
					}
					if(sum!=1){
						System.out.println("Warning0: "+sum);
					}
				}
				probs.put(a, probA);
			}
		} else if(noisyPositions.contains(field[row][collum].getPosition())) {
			double p = 1./(start.getActions().size());	//TODO Fehler integer division behoben 1 -> 1.
			
			for(Action a : start.getActions()) {
				HashMap<Action,Double> probA = new HashMap<Action,Double>();
				double sum=0;
				for(Action b : start.getActions()) {
					sum+=p;
					probA.put(b, p);
				}
				probs.put(a, probA);
				if(sum!=1){
					System.out.println("Warning1: "+sum);
				}
				
			}
			
			
		} else if (badPositions.contains(field[row][collum].getPosition())){
			
			double pR = (1-badProbability) / (start.getActions().size()-1);
			
			for(Action a : start.getActions()) {
				HashMap<Action,Double> probA = new HashMap<Action,Double>();
				double sum=0;
				for(Action b : start.getActions()) {
					
					if(b.equals(stay)) {
						probA.put(b,badProbability);
						sum+=badProbability;
					} else {
						probA.put(b, pR);
						sum+=pR;
					}
				}
				if(sum!=1){
					System.out.println("Warning2: "+sum);
				}
				probs.put(a, probA);
			}
			
		} else {
			
			double pR = (1-normalProbabaility) / (start.getActions().size()-1);
			for(Action a : start.getActions()) {
				HashMap<Action,Double> probA = new HashMap<Action,Double>();
				double sum=0;
				for(Action b : start.getActions()) {
					if(!a.equals(b)) {
						probA.put(b, pR);
						sum+=pR;
					} else {
						probA.put(b, normalProbabaility);
						sum+=normalProbabaility;
					}
				}
				if(sum!=1){
					System.out.println("Warning3: "+sum);
				}
				probs.put(a, probA);
			}
		}
		start.setProbabilities(probs);
	}
	
	public int getRows() {
		return rows;
	}

	public void setRows(int rows) {
		this.rows = rows;
	}

	public int getCollums() {
		return collums;
	}

	public void setCollums(int collums) {
		this.collums = collums;
	}

	public List<Position> getNoisyPositions() {
		return noisyPositions;
	}

	public void setNoisyPositions(List<Position> noisyPositions) {
		this.noisyPositions = noisyPositions;
	}

	public List<Position> getBadPositions() {
		return badPositions;
	}

	public void setBadPositions(List<Position> badPositions) {
		this.badPositions = badPositions;
	}

	public boolean isWalkOutAllowd() {
		return walkOutAllowd;
	}

	public void setWalkOutAllowd(boolean walkOutAllowd) {
		this.walkOutAllowd = walkOutAllowd;
	}

	public double getNearlyAbsorbing() {
		return badProbability;
	}

	public void setNearlyAbsorbing(double nearlyAbsorbing) {
		this.badProbability = nearlyAbsorbing;
	}

	public double getNormalProbabaility() {
		return normalProbabaility;
	}

	public void setNormalProbabaility(double normalProbabaility) {
		this.normalProbabaility = normalProbabaility;
	}

	public double getNoisyProbability() {
		return noisyProbability;
	}

	public void setNoisyProbability(double noisyProbability) {
		this.noisyProbability = noisyProbability;
	}

	public Position getGoalStatePosition() {
		return goalStatePosition;
	}

	public void setGoalStatePosition(Position goalStatePosition) {
		this.goalStatePosition = goalStatePosition;
		field[goalStatePosition.getRow()][goalStatePosition.getCollum()].setAbsorbing(true);
	}
	
	public List<State> getStateList() {
		return stateList;
	}
	
	public State getState(Position position) {
		return field[position.getRow()][position.getRow()];
	}
	
	public Position getRandomPosition() {
		Random r = new Random();
		int selectedRow = r.nextInt(field.length);
		int selectedCollum = r.nextInt(field[0].length);
		return field[selectedRow][selectedCollum].getPosition();
	}
}
